/*-----------------------------------------------------------------------------
		   Licensed to the Apache Software Foundation (ASF) under one
           or more contributor license agreements.  See the NOTICE file
           distributed with this work for additional information
           regarding copyright ownership.  The ASF licenses this file
           to you under the Apache License, Version 2.0 (the
           "License"); you may not use this file except in compliance
           with the License.  You may obtain a copy of the License at

             http://www.apache.org/licenses/LICENSE-2.0

           Unless required by applicable law or agreed to in writing,
           software distributed under the License is distributed on an
           "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
           KIND, either express or implied.  See the License for the
           specific language governing permissions and limitations
           under the License.  
-----------------------------------------------------------------------------*/

package data;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

/**
 * This class handles the report generation, and the re-structuring of folders
 * in a tree manner. It also handles scenarios where folders are not made due to
 * no files in the folder, though there are more sub folders. 
 * 
 * @author Kevin.J.Jalbert
 */
public class ReportGen {

	private ArrayList<SFolder> allFolders = null;
	private File rootFolder = null;

	/**
	 * Sets the root folder.
	 * 
	 * @param rootFolder the new rootfolder
	 */
	public void setRootFolder(File rootFolder) {
		this.rootFolder  = rootFolder;
	}

	/**
	 * Prints a quick summary report that only goes two levels deep in the folder
	 * structure.  <p> TODO this can be removed when the interactive 
	 * tree-structure is added.
	 * 
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void printReport() throws IOException {

		// Order the files into a folder structure
		orderFolders();

		// Make the top level rootfolder
		SFolder rootFolder = makeRootFolder();

		// Print out the information for the root folder
		System.out.println("---------------------------------------------------------------------------------");
		System.out.println("Folder: " + rootFolder.getFolderPath());
		System.out.println("Size: " + rootFolder.getFolderSize()/1024 + " KBs");			
		System.out.println("Source Lines of Code: " + rootFolder.getTotalSLOC());
		System.out.println("Comment Ratio: " + rootFolder.getCommentRatio());
		System.out.println("Total Cyclomatic Complexity: " + rootFolder.getTotalCC());
		System.out.println("Actual Source Lines of Code per Cyclomatic Complexity: " + rootFolder.getSLOCPerCC());
		System.out.println("Total Number of Files: " + rootFolder.getTotalFiles());
		System.out.println("::");
		System.out.println("Average Cyclomatic Complexity per File: " + rootFolder.getAverageCC());
		System.out.println("Average Comment Ratio per File: " + rootFolder.getAverageCR());
		System.out.println("Average Source Lines of Code per File: " + rootFolder.getAverageSLOC());
		System.out.println("Average Source Lines of Code per Cyclomatic Complexity: " + rootFolder.getAverageSLOCPerCC());
		System.out.println("::");
		System.out.println("Highest Cyclomatic Complexity for a File: " + rootFolder.getHighCC());
		System.out.println("Highest Comment Ratio for a File: " + rootFolder.getHighCR());
		System.out.println("Highest Source Lines of Code for a File: " + rootFolder.getHighSLOC());
		System.out.println("::");
		System.out.println("Lowest Cyclomatic Complexity for a File: " + rootFolder.getLowCC());
		System.out.println("Lowest Comment Ratio for a File: " + rootFolder.getLowCR());
		System.out.println("Lowest Source Lines of Code for a File: " + rootFolder.getLowSLOC());

		// Print information of the subfolders
		for(SFolder folder : rootFolder.getSubSFolders()){
			// Print out the information for this sub folder
			System.out.println("---------------------------------------------------------------------------------");
			System.out.println("Folder: " + folder.getFolderPath());
			System.out.println("Size: " + folder.getFolderSize()/1024 + " KBs");			
			System.out.println("Source Lines of Code: " + folder.getTotalSLOC());
			System.out.println("Comment Ratio: " + folder.getCommentRatio());
			System.out.println("Total Cyclomatic Complexity: " + folder.getTotalCC());
			System.out.println("Actual Source Lines of Code per Cyclomatic Complexity: " + folder.getSLOCPerCC());
			System.out.println("Total Number of Files: " + folder.getTotalFiles());
			System.out.println("::");
			System.out.println("Average Cyclomatic Complexity per File: " + folder.getAverageCC());
			System.out.println("Average Comment Ratio per File: " + folder.getAverageCR());
			System.out.println("Average Source Lines of Code per File: " + folder.getAverageSLOC());
			System.out.println("Average Source Lines of Code per Cyclomatic Complexity: " + folder.getAverageSLOCPerCC());
			System.out.println("::");
			System.out.println("Highest Cyclomatic Complexity for a File: " + folder.getHighCC());
			System.out.println("Highest Comment Ratio for a File: " + folder.getHighCR());
			System.out.println("Highest Source Lines of Code for a File: " + folder.getHighSLOC());
			System.out.println("::");
			System.out.println("Lowest Cyclomatic Complexity for a File: " + folder.getLowCC());
			System.out.println("Lowest Comment Ratio for a File: " + folder.getLowCR());
			System.out.println("Lowest Source Lines of Code for a File: " + folder.getLowSLOC());
		}
		System.out.println("---------------------------------------------------------------------------------");
	}

	/**
	 * Order the files into folders, and these folders into subfolders. The end
	 * result is a tree-structure that mimics the file structure of the rootfolder.
	 */
	private void orderFolders() {

		allFolders = new ArrayList<SFolder>();

		SFolder newFolder = null;
		Boolean alreadyFolder = false;

		// Group files into folders
		for (SFile file : CurrentTask.finishedFilesQueue){

			for (SFolder currentFolder : allFolders){

				// If the folder exist then add the file
				if (file.getFilePath().equals(currentFolder.getFolderPath())){
					alreadyFolder = true;
					currentFolder.addSFile(file);
					break;
				}
			}

			// If folder is does not exist then make it
			if (!alreadyFolder){

				// Add the file to new folder and add it to the allFolders arraylist
				newFolder = new SFolder(file);
				newFolder.addSFile(file);
				allFolders.add(newFolder);
			}
			else{ // Folder already exists, reset flag
				alreadyFolder = false;
			}

			// If the first folder is not created then make one using this file
			if(allFolders.size() == 0){
				newFolder = new SFolder(file);
				newFolder.addSFile(file);
				allFolders.add(newFolder);
			}
		}

		// Now match folders into sub folders
		for(SFolder childFolder : allFolders){
			// If this folder can be encapsulated in another folder by exactly one separator then sub folder it
			for(SFolder parentFolder : allFolders){
				// If the folders are different
				if (!childFolder.getFolderPath().equals(parentFolder.getFolderPath())){
					if (validSubFolder(childFolder.getFolderPath(),parentFolder.getFolderPath())){
						parentFolder.addSubSFolder(childFolder);
						childFolder.setParentFolder(parentFolder);
						break;
					}
				}				
			}
		}
	}

	/**
	 * Make a rootfolder based on the selected rootfolder that was chosen at the
	 * start to begin the analysis. The previous rootfolder that is found (occurs
	 * if there is a file in it) is removed as this one will replace it. Then the
	 * rootfolder is populated with files, and subfolders. 
	 * 
	 * @return rootFolder the rootfolder
	 */
	private SFolder makeRootFolder() {

		// Make the rootfolder
		SFolder rootFolder = new SFolder(new SFile(this.rootFolder, 0, 0, ""), true);

		// Look for a folder that might be the root and take the files from it
		for(int i = 0; i < allFolders.size(); i++){

			SFolder folder = allFolders.get(i);

			// If this folder matches the rootfolder add its files to root
			if (folder.getFolderPath().equals(rootFolder.getFolderPath())){

				for(SFile file : folder.getSFiles()){
					rootFolder.addSFile(file);
				}

				// Remove the 'fake' rootfolder
				allFolders.remove(i);
				break;
			}
		}

		// Populate the rootfolder now
		for(SFolder childFolder : allFolders){

			// If the childFolder is valid to be a sub folder of the parentfolder
			if (validSubFolder(childFolder.getFolderPath(),rootFolder.getFolderPath())){

				// Form the linking between the two folders
				rootFolder.addSubSFolder(childFolder);	
				childFolder.setParentFolder(rootFolder);
			}
		}	

		// Add the rootfolder to the allFolders collection
		rootFolder.setParentFolder(rootFolder);
		allFolders.add(rootFolder);

		// Fix lost children folders that had no files in the folder yet there are subfolders
		fixLostChildrenFolders();

		return rootFolder;
	}

	/**
	 * Check to see if the child folder is a valid sub folder of the possible
	 * parent folder, by checking the filepaths.
	 * 
	 * @param possibleChild the possible child folder
	 * @param possibleParent the possible parent folder
	 * 
	 * @return valid is true if child is a valid sub folder
	 */
	private boolean validSubFolder(String possibleChild, String possibleParent) {

		boolean valid = false;

		// The parent's path length can not be longer then the child's
		if (possibleChild.length() < possibleParent.length()){
			valid = false;
		}
		else{

			// Check to make sure filepaths are similar
			if(!possibleChild.contains(possibleParent)){
				valid = false;
			}
			else{

				// Remove the commonality
				possibleChild = possibleChild.replace(possibleParent, "");

				// Now remove the only the first file separator
				possibleChild = possibleChild.replaceFirst(File.separator, "");

				// If there are no separators left then this is a subfolder
				if(possibleChild.contains(File.separator)){
					valid = false;
				}
				else{
					valid = true;
				}
			}
		}	
		return valid;
	}

	/**
	 * Fixes the lost children folders, which are folders that never got linked to
	 * a parent since there was no file in the folder above them. The solution to this
	 * is to find the closet parent folder and link it, if one doesn't exist make it.
	 * This is done till all folders with no parents are gone.
	 */
	private void fixLostChildrenFolders() {

		for(int i = 0; i < allFolders.size(); i++){
			SFolder lostChildFolder = allFolders.get(i);

			// No parent means a lost childrenfolder
			if(lostChildFolder.getParentFolder() == null){

				// Find the closest parent if possible
				closestParent(lostChildFolder);

				// Now match folders into sub folders again (a new folder might have been added)
				for(SFolder childFolder : allFolders){

					// If this folder can be encapsulated in another folder by exactly one separator then sub folder it
					for(SFolder parentFolder : allFolders){

						// If the folders are different
						if (!childFolder.getFolderPath().equals(parentFolder.getFolderPath())){

							if (validSubFolder(childFolder.getFolderPath(),parentFolder.getFolderPath())){

								// Folder isn't already a subfolder
								if (!parentFolder.getSubSFolders().contains(childFolder)){

									// Perform linkings of the folders
									parentFolder.addSubSFolder(childFolder);
									childFolder.setParentFolder(parentFolder);
									break;
								}
							}
						}				
					}
				}
				allFolders.remove(i);
				i = 0;
			}
		}
	}

	/**
	 * Find the closest parent folder of a lost child folder and add the child to
	 * it. If there is no match then make the parent folder and add the child to
	 * the newly created folder, then try again. This keeps going till the child
	 * folder is matched to an existing folder.
	 * 
	 * @param lostChildFolder the lost child folder
	 */
	private void closestParent(SFolder lostChildFolder) {

		String folderPath = lostChildFolder.getFolderPath();
		boolean matched = false;
		String modFolderPath = "";

		// Removes the last file separator and prepare to find next possible folder
		modFolderPath = folderPath.substring(0, folderPath.lastIndexOf(File.separator)); 

		// Finds the next possible parent folder
		modFolderPath = modFolderPath.substring(0, modFolderPath.lastIndexOf(File.separator)) + File.separator;

		// Make a filler folder that forms a link between the child and the parent
		SFolder fillerFolder = new SFolder(new SFile( new File(modFolderPath + "a" + File.separator), 0, 0, ""));
		fillerFolder.addSubSFolder(lostChildFolder);
		lostChildFolder.setParentFolder(fillerFolder);

		// Loop till a match is made
		while(!matched){

			for(SFolder possibleParentFolder : allFolders){

				// If the modified folder is a valid childof the possible parent
				if (validSubFolder(modFolderPath,possibleParentFolder.getFolderPath())){

					// Subfolder the filler folder
					possibleParentFolder.addSubSFolder(fillerFolder);
					fillerFolder.setParentFolder(possibleParentFolder);
					matched = true;
					allFolders.add(fillerFolder);
					break;
				}
			}

			// No match has been made
			if(!matched){

				// Make a folder that forms a link
				fillerFolder.addSubSFolder(lostChildFolder);
				lostChildFolder.setParentFolder(fillerFolder);
				allFolders.add(fillerFolder);

				// Acquire the next possible folder
				modFolderPath = modFolderPath.substring(0, modFolderPath.lastIndexOf(File.separator));
				modFolderPath = modFolderPath.substring(0, modFolderPath.lastIndexOf(File.separator)) + File.separator;						
				fillerFolder = new SFolder(new SFile( new File(modFolderPath + "a" + File.separator), 0, 0, ""));
			}
		}
	}
}